/**
 * 
 */
package com.momosw.games.jericho.board;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import com.momosw.games.engine.Game;
import com.momosw.games.engine.events.Event;
import com.momosw.games.engine.events.FeedSupplyEvent;
import com.momosw.games.engine.player.Player;
import com.momosw.games.engine.simulator.Normalizer;
import com.momosw.games.engine.util.Utils;
import com.momosw.games.engine.util.WallMeasurement;

/**
 * <b>Project:</b> JerichoCardGame<br />
 * <b>Package:</b> com.momosw.games.jericho.board<br />
 * <b>Class:</b> Board.java<br />
 * <br />
 * <i>GSI 2011</i><br />
 *
 * @author Miguel Coronado (miguelcb84@gmail.com)
 * @version	Aug 20, 2011
 *
 */
public class Board implements Event{

    /** The associate game*/
    private Game game;
    /** */
    Map <Player, Side> playedInFront;
    /** The list of the scoring phases */
    List<Scoring> scoring;
    /** The draw deck */
    private Deck deck;
    /** The supply pile */
    private Deck supply;
    
    /**
     * Constructor.
     * @param players            game.scoringPhase ();

     */
    public Board (Game game, List<Player> players){
        this.game = game;
        // Initialize decks
        this.supply = new Deck();
        this.deck = new Deck(game.getConfig());
        // Reset the cards player in front of the players
        playedInFront = new HashMap<Player, Side>();
        scoring = new LinkedList<Scoring>();
        // Prepare the sides
        for (Player player : players) {
            playedInFront.put(player, new Side());
//            scoring.score.put(player, new HashSet<Card>());
        }
        
    }
    
    /**
     * @return the deck
     */
    public Deck getDeck() {
        return deck;
    }

    /**
     * @return the supply
     */
    public Deck getSupply() {
        return supply;
    }

    /**
     * This puts in play in from of the player given the card given as part 
     * of the appropriate wall.
     * @param player
     * @param card
     */
    public void putInPlay (Player player, Card card) {
        if(card instanceof WallCard){
            playedInFront.get(player).putCard(card, ((WallCard) card).getWallType());
            return;
        }
        throw new IllegalArgumentException();
    }
    
    /**
     * This puts in play in from of the player given the card given as part 
     * of the wall given.
     * 
     * If it is a Trumpet Card it performs the appropriate actions.
     * 
     * @param player
     * @param card
     * @param wall
     */
    public void putInPlay (Player player, Card card, WallType wallType) {
        // Check if it is a Trumpets card
        if (!( card instanceof TrumpetsCard )) {
            playedInFront.get(player).putCard(card, wallType);
        }
        
        // It is a Trumpet card
        int maxLenght = 0;
        Set<Player> aux = new HashSet<Player>();
        
        // Find the longest wall
        for (Player auxPlayer : playedInFront.keySet()) {
            List<Card> wall = this.playedInFront.get(auxPlayer).getWall(wallType);
            int lenght = 0;
            for (Card auxCard : wall) {
                if (lenght < auxCard.getLenght() && !(auxCard instanceof TrumpetsCard)) {
                    lenght = auxCard.getLenght();
                }
            }
            if (maxLenght < lenght) {
                maxLenght = lenght;
                aux = new HashSet<Player>();
            }
            if (maxLenght == lenght) {
                aux.add(auxPlayer);
            }
        }
        
        // Remove all the wall cards
        for (Player auxPlayer : aux) {
            List<Card> wall = playedInFront.get(auxPlayer).getWall(wallType);
            
            // Block to remove all the wall cards of the same length
            while (wall.contains(new WallCard (wallType, maxLenght))){
                wall.remove(new WallCard(wallType, maxLenght));
                supply.addElement(new WallCard(wallType, maxLenght));
                Utils.notifyPlayers(game, new FeedSupplyEvent(new WallCard(wallType, maxLenght)));
            }
            checkTrumpetsRemoval(wall);
        }
        
        // Place the trumpet card in the right place
        Side side = playedInFront.get(player); 
        if (side.getWall(wallType).size() != 0) {
            side.putCard(card, wallType);
        }
        else {
            this.supply.add(new TrumpetsCard());
            Utils.notifyPlayers(game, new FeedSupplyEvent(new TrumpetsCard()));
        }
    }

    /**
     * 
     * @param wall
     */
    private void checkTrumpetsRemoval (List<Card> wall) {
        for (Card card : wall) {
            if (card instanceof WallCard) {
                return;
            }
        }
        
        // no wall card found, so only trumpets, so put them on the supply 
        for (Card card : wall){
            this.supply.add(card);
            Utils.notifyPlayers(game, new FeedSupplyEvent(card));
        }
        // and empty the wall
        wall.clear();
        
    }
    
    /**
     * 
     * @param player
     * @param wallType
     * @return
     */
    public List<Card> getWall (Player player, WallType wallType) {
        return playedInFront.get(player).getWall(wallType);
    }
    
    /**
     * This might not be here
     * @return
     */
    public String getBoardInform () {
        Set<Player> keySet = playedInFront.keySet();
        String res =  keySet.size() + " jugadores:\r\n";
        
        Normalizer forPlayers = new Normalizer(keySet);
        Map<WallType,Normalizer> prettyPrint = new HashMap <WallType,Normalizer> ();
        for (WallType wallType : WallType.getTypes()) {
            List<List<Card>> collection = new LinkedList<List<Card>>();
            for(Player player : keySet){
                collection.add(playedInFront.get(player).getWall(wallType));
            }
            prettyPrint.put(wallType, new Normalizer(collection));
        }
        
        for (Player player : keySet) {
            res += " * " + forPlayers.normalize(player) + ": ";
            for (WallType wallType : WallType.getTypes()){
                res += wallType.toString();
                res += prettyPrint.get(wallType).normalize(playedInFront.get(player).getWall(wallType));
            }
            res += "\r\n";
        }
        
        res += this.supply.size() + " cards in supply pile.";
        return res;
    }
    
    /**
     * Return the number of scoring phases already played
     * @return  the size of the list of Scoring Phases 
     */
    public int getScoringCount() {
        return scoring.size();
    }
    
    /**
     * 
     * @return
     */
    public List<Scoring> getScoring() {
        return this.scoring;
    }
    
    public Player getCurrentPlayer() {
        return this.game.getCurrentPlayer();
    }
    
    /**
     * The parameters provide information about the player with the longest 
     * wall for each wall-type and the amount of cards in the supply pile
     * for each wall-type (and the exact cards in the pile, in order to allow
     * this method to remove them from the supply Deck).
     * 
     * Thus, this method distribute the cards among players. It gives each 
     * player gains the cards that he earned and it removes those cards from 
     * the supply pile. In case a cards does not belong to any player that 
     * card remains in the supply pile.
     * 
     * @param wallMeasurement
     * @param supplyCards
     * @return
     */
    public Scoring setScoring (WallMeasurement wallMeasurement, Map<WallType, Stack<Card>> supplyCards){
        
        Scoring scoring = new Scoring(playedInFront.keySet()); // Initialize with player list
        
        for (WallType wallType : WallType.getTypes()){ // For each wall-type 
            
            // Get the player with the longest wall of the given type
            Set<Player> players = wallMeasurement.getLongestPlayer(wallType);
            if (players.size() == 0){
                continue; // carry on with other color
            }
            // Gather all the cards from the supply pile of the given type
            Stack<Card> cardsOFAType = supplyCards.get(wallType);
            int add = cardsOFAType.size()/players.size(); // count the cards

            for (Player player : players) { // For each player
                // give him the fair amount of cards
                for (int amount = 0; amount < add; amount++){
                    Card cardToPlayer = cardsOFAType.pop(); // Get the cards
                    supply.remove(cardToPlayer); // remove it from the supply
                    scoring.addCard(player, cardToPlayer); // to the player
                }
            }

        }
        
        this.scoring.add(scoring);
        
        return scoring;
    }
    
    /**
     * 
     * @return
     */
    public Scoring lastScoring (){
        Scoring scoring = new Scoring (playedInFront.keySet()); // Initialize with player list
        for(Player player : this.playedInFront.keySet()){
            // Add all the one sized cards in play
            Side side = playedInFront.get(player);
            for(Card card : side.getAllCardsInPlay()){
                if(card.getLenght() == 1){
                    scoring.addCard(player, card);
                }
            }
            // Add all the one sized cards in hand
            for(Card card : player.getHand()){
                if(card.getLenght() == 1){
                    scoring.addCard(player, card);
                }
            }
        }
        
        this.scoring.add(scoring);
        
        return scoring;
    }

    public Event hidePrivate() {
        return this;
    }
}